Análisis de películas: Hito 3

Cristopher Cid, Felipe Meyer, Camila Santelices

28 de Junio de 2019

Introducción

A través de los años, la industria del entretenimiento siempre ha ido en crecimiento, siendo la del cine una de las más grandes (solo siendo superada por los videojuegos) en cuanto a ganancias. Es por esto, que el análisis de las películas, de acuerdo a sus diversas cualidades, tales como momento histórico de su desarrollo, presupuesto, género, actores involucrados, entre otros, puede resultar de gran utilidad para la búsqueda del éxito en un medio donde las ‘grandes’ potencias pertenecen a un círculo muy cerrado. Además de que en torno al séptimo arte existen infinidades de personas críticas, se genera la necesidad de información acerca de qué es lo que le gusta a la gente y qué no, es por esto que uno de los objetivos es encontrar el modelo de película que fuera infalible. En el fondo, la existencia de los reviews y su análisis en conjunto, otorga una herramienta poderosa para la posible toma de decisiones de productores, inversionistas, o simplemente si incorporarse a un film por su expectativa calculada a partir de datos históricos y contextualizados. Sin embargo, la existencia de una cantidad de datos tan grandes, no vienen listos para exigirles información implícita de forma simple, por lo que la minería de datos es la herramienta principal en el estudio que será desarrollado en torno a las películas. Además, esta herramienta entrega una forma de conseguir información, que cualquier ser humano le resultaría difícil de visualizar, por ejemplo, la existencia de relaciones entre conceptos que ante nosotros resultan ajenos, para un cierto algoritmo pueden resultar evidentes; en resumen se busca encontrar patrones en la industria que a simple vista son imposibles de distinguir. Por otra parte, las motivaciones vienen principalmente de analizar una temática muy ligada a nuestra cultura como lo son las películas. Es de gran interés para la gente ver las características de las películas con mejor y peor aceptación del público, mostrando los efectos del lenguaje, dinero invertido, duración, etc, en la forma en la que llega a la gente el arte cinematográfica. Este dataset contiene muchos atributos que podrían usarse para poder predecir características relevantes de películas de distintas culturas, presupuesto y temática.

Temática y descripción de datos

Se quiere entonces comprobar qué atributos y en qué medida afectan al éxito de una película. Para esto, se utilizará "vote_average" (promedio de votos) como parámetro que determinará si una película es exitosa o no. Se realizarán variados experimentos que aportarán a responder la problemática propuesta.

Características del dataset

  • El dataset cuenta con 45466 películas observadas.
  • Cada registro contiene 24 atributos, de los cuales los más importantes son el nombre de la película, su fecha de estreno, presupuesto, ingresos, voto promedio, cantidad de votos, entre otros.
  • La mediana del presupuesto y de las ganancias de las películas es 0.
  • Solo hay 9 películas para adultos en el dataset. Los datos fueron obtenidos de Kaggle, en https://www.kaggle.com/rounakbanik/the-movies-dataset.

Hay muchos elementos que no tienen sentido económico, por ejemplo que el presupuesto sea igual a 4 en muchas observaciones, ya que es poco probable tener iguales presupuestos para tantas películas y más un valor tan bajo para la industria. Esto hacer pensar que en aquellos datos hubieron errores de medición u obtención, llevando a datos erróneos.

A continuación se mostrará el análisis de los datos efectuado.

In [37]:
from IPython.display import Image
Image("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\img2.png")
Out[37]:

Con y sin outliers la distribución se mueve, permitiendo observarla mejor.

In [149]:
Image("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\img3.png")
Out[149]:
In [150]:
Image("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\img4.png")
Out[150]:

Boxplot de promedio por grupo de presupuesto para dos bases distintas.

In [151]:
Image("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\img5.png")
Out[151]:
In [152]:
Image("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\img6.png")
Out[152]:

En el primer gráfico se puede apreciar que el voto promedio por película no cambia significativamente entre grupos de presupuesto. Como se había mencionado antes, los datos cuyo presupuesto era 4 millones de dólares o más bajo, podrían estar errados por lo que se filtran y se genera un nuevo gráfico. Esto es lo que se muestra en el segundo boxplot. Como se ve, el primer grupo aumenta un poco su voto promedio pero en general los grupos se mantienen constantes entre ellos.

In [153]:
Image("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\img7.png")
Out[153]:
In [154]:
Image("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\img8.png")
Out[154]:

En el primer gráfico se muestra la cantidad de votos hechos para un voto promedio (nota promedio), donde cada punto representa una película en específico. En el segundo gráfico se muestra la cantidad de votos usando el mismo subset anterior de presupuesto mayor a 4 millones de dólares. De ambos gráficos es rescatable que bajos puntajes(o notas) fueron determinados por una baja cantidad de votos y que en cambio a medida que aumenta la nota, la cantidad de votantes que la determinaron fue mayor.

In [38]:
Image("C:\Users\Marcela\Downloads\mineriadedatos\img9.png")
Out[38]:

Acá se muestra la matriz de covarianza de los atributos numéricos, que serán usados para los experimentos posteriores.

Tratamiento de datos

Como se mostró anteriormente, hay muchos datos faltantes o iguales a cero en atributos como Presupuesto o Ingresos, los cuales se eliminarán en ciertas partes del la realización de experimentos. Además de quitar outliers y datos mal ingresados, como películas con índice de popularidad mayores a 300. Es necesario recordar que aquellas partes, se realizarán removiendo las cerca de 35.000 filas que tenían datos faltantes o nulos, por lo que el análisis podría estar sesgado y no es concluyente de exactitud, tal y como en el caso de haber realizado regresiones por ejemplo, no se podría hablar de causalidad, sólo de correlación.

Experimentos

Se realizaron tres experimentos. En primer lugar, se hizo un clustering usando k-means para observar posibles asociaciones entre películas según su nivel de exito en votos y popularidad, y en base a ese clustering se creó una regla de asociación usando el atributo de "genres" (géneros de las películas). Posteriormente, se realizaron algoritmos de clasificación, árboles de decisión y K-nearest neighbors, utilizando las variables numéricas del dataset. Luego, se realizó un árbol de decisión agregando a los atributos anteriores las variables categóricas. El propósito de estos clasificadores es identificar si los atributos disponibles pueden ser usados para poder predecir el voto promedio de una película (criterio de éxito).

Clustering y regla de asociación

A continuación, se realizará el clustering de los datos numéricos, y posteriormente se usará el resultado para poder efectuar la regla de asociación con el atributo "genres".

In [3]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from ast import literal_eval
from sklearn import preprocessing
from sklearn.cluster import KMeans

Leemos el dataset de película:

In [4]:
md = pd.read_excel("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\movies.xlsx")
md_num = md[['title','genres','budget','revenue','popularity','runtime','vote_average','vote_count']]
md_num['genres'] = md_num['genres'].fillna('[]').apply(literal_eval).apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])
md_num = md_num.infer_objects()
md_num.dropna(inplace=True)
C:\ProgramData\Anaconda2\lib\site-packages\ipykernel_launcher.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  This is separate from the ipykernel package so we can avoid doing imports until

Leemos los datos desde excel, y transformamos los datos de genero a un formato utilizable en forma de listas. Se eliminan los NA, además de transformar los datos a numericos. Luego filtramos por atributos numericos mayores a 0 y que tengan una cantidad de votos mayores a 10, además de normalizar los datos para utilizar kmeans de forma correcta. Con esto se obtienen 5092 datos de películas.

In [5]:
md_num_filter = md_num[md_num['vote_count']>10]
md_num_filter = md_num_filter[md_num_filter['budget']>0]
md_num_filter = md_num_filter[md_num_filter['revenue']>0]
minmax2 = preprocessing.MinMaxScaler().fit_transform(md_num_filter.drop(['title', 'genres'],axis=1))
md_norm_filter = pd.DataFrame(minmax2, index=md_num_filter.index, columns=md_num_filter.columns[2:])
md_norm_filter.shape
Out[5]:
(5092, 6)
In [6]:
minmax = preprocessing.MinMaxScaler().fit_transform(md_num.drop(['title', 'genres'],axis=1))
md_norm = pd.DataFrame(minmax, index=md_num.index, columns=md_num.columns[2:])
md_num_filter.head()
Out[6]:
title genres budget revenue popularity runtime vote_average vote_count
0 Toy Story [Animation, Comedy, Family] 30000000 373554033.0 21.946943 810.0 77.0 5415.0
1 Jumanji [Adventure, Fantasy, Family] 65000000 262797249.0 17.015539 1040.0 69.0 2413.0
3 Waiting to Exhale [Comedy, Drama, Romance] 16000000 81452156.0 3.859495 1270.0 61.0 34.0
5 Heat [Action, Crime, Drama, Thriller] 60000000 187436818.0 17.924927 1700.0 77.0 1886.0
8 Sudden Death [Action, Adventure, Thriller] 35000000 64350171.0 5.231580 1060.0 55.0 174.0

A continuación, se construirá el grafico de numero de clusters vs score (k-means), para encontrar el codo en tal gráfico, y de esta forma utilizar un $k$ apropiado para el método de clustering k-means.

In [7]:
k = range(1, 15)
kmeans = [KMeans(n_clusters=i) for i in k]
score = [kmeans[i].fit(md_norm).score(md_norm) for i in range(len(kmeans))]
In [8]:
plt.figure(figsize=(15,6))
plt.plot(k,score)
plt.xlabel('Numero de clusters')
plt.ylabel('Score')
plt.title('Elbow')
plt.xticks(range(1, 15), range(1, 15))
plt.show()

Se observa que está cercano al valor 5 ó 6. Se utilizará $k = 5$ para hacer el análisis. Aquí se agregará el label de los clusters encontrados a cada película, para ver estimaciones de cómo se construyeron.

In [9]:
kmeans = KMeans(n_clusters=5, random_state=420)
kmeans.fit(md_norm_filter)
md_num_filter['cluster'] = kmeans.labels_
md_norm_filter['cluster'] = kmeans.labels_
A= md_norm_filter.groupby(['cluster']).mean()
np.sqrt(np.square(A).sum(axis=1))
Out[9]:
cluster
0    0.663821
1    0.728352
2    1.033248
3    0.509164
4    0.818265
dtype: float64

Aquí vemos las métricas de los cluster resultantes, con esto podemos determinar la forma en que fueron agrupadas, es decir, decimos que a mayor métrica mejor es la película, con esto definimos la pelicula que pertenece al cluster como:

0: mala

1:neutral

2:excelente

3:muy mala

4:buena

In [70]:
movie=md_num_filter[['genres', 'cluster']]
movie.head()
Out[70]:
genres cluster
0 [Animation, Comedy, Family, 4] 4
1 [Adventure, Fantasy, Family, 1] 1
3 [Comedy, Drama, Romance, 0] 0
5 [Action, Crime, Drama, Thriller, 4] 4
8 [Action, Adventure, Thriller, 3] 3
In [71]:
genres= movie['genres'].tolist()
cluster= movie['cluster'].tolist()
final=[]
c=0
for i in genres:
    a=i
    a.append(str(cluster[c]))
    final.append(a)
    c+= 1 

Creamos una lista que contiene el género y su cluster para poder crear reglas de asociación. De aquí resultan 51 reglas, pero se escogen solo la que contengan como consecuente al cluster, para así caracterizar los géneros con respecto al éxito (de acuerdo al cluster).

In [72]:
from apyori import apriori
association_rules = apriori(final, min_support=0.015, min_confidence=0.4, min_lift=2, min_length=2)  
association_results = list(association_rules)
print(len(association_results))
51
In [73]:
for item in association_results:
    pair = item[0] 
    items = [x for x in pair]
    if (items[1]=='0' or items[1]=='1' or  items[1]=='2' or items[1]=='3' or items[1]=='4'):
        print("Rule: " + items[0] + " -> " + items[1])
        print("Support: " + str(item[1]))
        print("Confidence: " + str(item[2][0][2]))
        print("Lift: " + str(item[2][0][3]))
        print("=====================================")
Rule: Action -> 2
Support: 0.0194422623723
Confidence: 0.60736196319
Lift: 2.25743585151
=====================================
Rule: Action -> 0
Support: 0.0280832678712
Confidence: 0.556420233463
Lift: 2.0680962254
=====================================
Rule: Mystery -> 0
Support: 0.0231736056559
Confidence: 0.651933701657
Lift: 2.26751803882
=====================================
Rule: Action -> 2
Support: 0.0157109190888
Confidence: 0.808080808081
Lift: 4.37739093058
=====================================
Rule: Action -> 3
Support: 0.0221916732129
Confidence: 0.653179190751
Lift: 2.42772878781
=====================================
Rule: Romance -> 3
Support: 0.0233699921445
Confidence: 0.708333333333
Lift: 2.0764728459
=====================================
Rule: Drama -> 4
Support: 0.0219952867243
Confidence: 0.565656565657
Lift: 2.15110024819
=====================================
Rule: Mystery -> 4
Support: 0.0157109190888
Confidence: 0.672268907563
Lift: 2.33824677412
=====================================
Rule: Action -> 0
Support: 0.0223880597015
Confidence: 0.699386503067
Lift: 2.43256562406
=====================================
Rule: Drama -> 0
Support: 0.0261194029851
Confidence: 0.473309608541
Lift: 2.58316455165
=====================================
Rule: Drama -> 0
Support: 0.0255302435192
Confidence: 0.622009569378
Lift: 2.16343765524
=====================================
Rule: Drama -> 4
Support: 0.0188531029065
Confidence: 0.5
Lift: 3.05642256903
=====================================

Romance se asocia con el cluster 3 (películas muy malas), con un soporte de un 2,3% y una confianza de 0,71. Las películas de acción se asocian tanto con las películas excelentes como con las malas, sin embargo, las reglas de asociación con el cluster 3 (películas malas) tienen un mayor soporte que las reglas con las películas malas, sin embargo estas últimas contienen una mayor confianza en promedio. Las películas de drama están asociadas al cluster 4 (películas buenas) y 0 (películas malas), con una confianza y soporte parecidos.

Modelo de clasificación - Árbol de decisión

Para los procesos de clasificación y previo a este, se construirá una variable que asigna una clase de acuerdo a un rango del atributo promedio de votación (vote_average) y la cual será la variable a clasificar. Se graficaron los votos para tener una idea de cuántas clases crear y distribuir aproximadamente de manera equitativa. La siguiente imagen muestra los cortes, sin embargo, los primeros dos grupos se juntaron debido a que eran menos cantidades de datos.

In [57]:
Image("C:\Users\Marcela\Downloads\mineriadedatos\img10.png")
Out[57]:

Finalmente, los grupos creados fueron los siguientes, siendo la clase 1 la de menor voto promedio, es decir la menos valorada y la clase 4 la de mayor voto promedio. Destacar que se usa la misma base filtrada utilizada en la segunda parte del clustering, es decir 5092 filas de datos.

  • Clase 1: [0, 36.4]
  • Clase 2: (36.4, 54.6]
  • Clase 3: (54.6, 72.8]
  • Clase 4: (72.8, 100]
In [58]:
md_voteavg = pd.read_excel("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\peliculas_modificado.xlsx")
md_voteavg_num = md_voteavg[['budget','revenue','popularity','runtime', 'clase_voto']]
md_voteavg_num = md_voteavg_num.infer_objects()
md_voteavg_num.dropna(inplace=True)
md_voteavg_numf = md_voteavg_num[md_voteavg_num['budget']>4]
md_voteavg_numf = md_voteavg_num[md_voteavg_num['revenue']>4]
md_voteavg_numf = md_voteavg_num[md_voteavg_num['popularity']>0]
md_voteavg_numf = md_voteavg_num[md_voteavg_num['runtime']>0]

Se crea el árbol de decisión usando el 60% de los datos como datos de entrenamiento:

In [59]:
from sklearn.neighbors import KNeighborsClassifier  
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report

md_num_train, md_num_test = train_test_split(md_voteavg_numf, test_size=.60, random_state = 37)
md_num_trFeatures = md_num_train.values[: , 0:4]
md_num_trClass = md_num_train.values[: , 4]

clf = DecisionTreeClassifier()
clf.fit(md_num_trFeatures,md_num_trClass)
Out[59]:
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')
In [60]:
md_num_teFeatures = md_num_test.values[: , 0:4]
md_num_teClass = md_num_test.values[: , 4]

md_predict = clf.predict(md_num_teFeatures)

print "Accuracy: " + str(accuracy_score(md_num_teClass, md_predict))
print classification_report(md_num_teClass, md_predict)
Accuracy: 0.437751493720278
             precision    recall  f1-score   support

     clase1       0.07      0.08      0.08       927
     clase2       0.29      0.29      0.29      6023
     clase3       0.60      0.58      0.59     14143
     clase4       0.19      0.20      0.19      3510

avg / total       0.44      0.44      0.44     24603

Modelo de clasificación K-Nearest Neighbors

Ahora se hará el clasificador con KNN:

In [61]:
clf_knn = KNeighborsClassifier(n_neighbors=5)  
clf_knn.fit(md_num_trFeatures,md_num_trClass)
Out[61]:
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=1, n_neighbors=5, p=2,
           weights='uniform')
In [62]:
md_predict = clf_knn.predict(md_num_teFeatures)
print "Accuracy: " + str(accuracy_score(md_num_teClass, md_predict))
print(classification_report(md_num_teClass, md_predict))
Accuracy: 0.49392350526358575
             precision    recall  f1-score   support

     clase1       0.08      0.05      0.06       927
     clase2       0.29      0.30      0.29      6023
     clase3       0.59      0.71      0.65     14143
     clase4       0.23      0.06      0.09      3510

avg / total       0.45      0.49      0.46     24603

Los resultados obtenidos no son muy positivos, el accuracy, la precisión y el recall son todos muy bajos. Hasta ahora no se obtiene un clasificador muy bueno utilizando solo los datos numéricos.

Modelos anteriores sin presupuesto ni ingresos

Ahora se repite lo anterior pero quitando budget y revenue:

In [63]:
md_voteavg = pd.read_excel("C:\\Users\\Marcela\\Downloads\\mineriadedatos\\peliculas_modificado.xlsx")
md_voteavg_num2 = md_voteavg[['popularity','runtime', 'clase_voto']]
md_voteavg_num2 = md_voteavg_num2.infer_objects()
md_voteavg_num2.dropna(inplace=True)
md_voteavg_num2 = md_voteavg_num2[md_voteavg_num2["popularity"]>0]
md_voteavg_num2 = md_voteavg_num2[md_voteavg_num2["runtime"]>0]
In [64]:
from sklearn.neighbors import KNeighborsClassifier  
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report

md_num_train, md_num_test = train_test_split(md_voteavg_num2, test_size=.60, random_state = 37)
md_num_trFeatures = md_num_train.values[: , 0:2]
md_num_trClass = md_num_train.values[: , 2]

clf = DecisionTreeClassifier()
clf.fit(md_num_trFeatures,md_num_trClass)
Out[64]:
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')
In [65]:
md_num_teFeatures = md_num_test.values[: , 0:2]
md_num_teClass = md_num_test.values[: , 2]

md_predict = clf.predict(md_num_teFeatures)

print accuracy_score(md_num_teClass, md_predict)
print classification_report(md_num_teClass, md_predict)
0.43287403975124983
             precision    recall  f1-score   support

     clase1       0.06      0.07      0.06       927
     clase2       0.28      0.28      0.28      6023
     clase3       0.60      0.58      0.59     14143
     clase4       0.18      0.19      0.18      3510

avg / total       0.44      0.43      0.44     24603

In [66]:
clf_knn = KNeighborsClassifier(n_neighbors=5)  
clf_knn.fit(md_num_trFeatures,md_num_trClass)
Out[66]:
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=1, n_neighbors=5, p=2,
           weights='uniform')
In [67]:
md_predict = clf_knn.predict(md_num_teFeatures)
print accuracy_score(md_num_teClass, md_predict)
print(classification_report(md_num_teClass, md_predict))
0.4938422143641019
             precision    recall  f1-score   support

     clase1       0.07      0.04      0.05       927
     clase2       0.29      0.29      0.29      6023
     clase3       0.60      0.72      0.65     14143
     clase4       0.26      0.07      0.11      3510

avg / total       0.45      0.49      0.46     24603

A partir de estos resultados, se puede ver que la accuracy, precisión y recall no difieren en gran medida. Todos los clasificadores hechos no han tenido buenos resultados, por lo que se puede deducir que las variables numéricas no son suficientes para poder efectuar un buen clasificador.

Árbol que incluye variables como género o inglés

Primero que todo se cargaron las librerías necesarias y la base de datos a usar.

library(readxl)
library(rpart)    #Modelo Machine Learning Arbol o Rpart
library(rattle)   #FancyRpartplot  grafico arbol
## Rattle: A free graphical interface for data science with R.
## Versión 5.2.0 Copyright (c) 2006-2018 Togaware Pty Ltd.
## Escriba 'rattle()' para agitar, sacudir y  rotar sus datos.
library(dplyr)    #Funcion if_else y para trabajar tablas
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
library(dummies)
## dummies-1.5.6 provided by Decision Patterns
peliculas3 <- read_excel("C:\\Users\\Marcela\\Downloads\\peliculas3.xlsx")

Además, se filtraron filas que no aportaban utilidad ya que no tenían el presupuesto o las ganancias asociadas, entre otros. Quedando finalmente 5.196 filas con 11 variables. Luego se quitaron atributos que no se usarían para la clasificación como production company y popularidad, siento está última otro análisis equivalente de clasificación como lo es clasificar copor vote_average que es lo que se clasificará en esta oportunidad.

#filtras datos (filas) que no sirven  
dataset<-peliculas3
dataset<-dataset[dataset$budget!=0, ]
dataset<-dataset[dataset$revenue!=0, ]
dataset<-na.omit(dataset) #sacar satos con NA's

#indicar formato correcto
dataset$budget<-as.numeric(dataset$budget)
dataset$popularity<-as.numeric(dataset$popularity)
dataset$genero<-as.factor(dataset$genero)
dataset$prod_company<-as.factor(dataset$prod_company)

#filtrar algunas columnas 
dataset<-dataset[ ,-9] #sacar production company
dataset<-dataset[ ,-2] #sacar popularity

Se quiere incorporar el lenguaje de la película, en particular, interesa saber si su lenguaje es inglés (dado que es el más presente) u otro idioma. Para lo anterior, se crea la variable ingles que vale 1 si dicha película está en ingles y 0 sino.

#crear variable ingles
dataset$ingles<-0
n=nrow(dataset)
for (i in 1:n){
  if (dataset$original_language[i]=="en"){
    dataset$ingles[i]<-1
  }
  else {
    dataset$ingles[i]<-0
  }
}
dataset<-dataset[ ,-9] #borrar variable original_languaje

Luego, se procede a la creación de clases asignando una película a uno de cuatro grupos según su “vote_average” o voto promedio, es decir, según la nota que los usuarios pusieron a dicha película. Para esto, se gráficaron los distintos vote_average y se intentó que quedarán equilibrados en cantidad de puntos, siempre y cuando respetaran un rango razonable de tal nota.

#gráfico para separar en partes(grupos) la variable vote_average según densidad de puntos. 
#Se probó para distintos "breaks".
ggplot(dataset, aes(x = cut(vote_average, breaks = 5), y = vote_average)) + 
  geom_point() +
  ggtitle("Grupos para el promedio de los votos")

. El gráfico anterior se probó para distintos“breaks” (cortes) donde se decidió juntar los dos primeros grupos dado su baja densidad de puntos y los demás se mantuvieron. De esta forma, se definieron 4 clases, donde la clase 1 se refiere a películas con bajo nota (“malas”" peliculas) y la clase 4 para películas de alto voto promedio(alta nota).

#crear variable clase_votos vacía
dataset$clase_votos<-"clase1"
#llenar variable
nclase1=0
nclase2=0
nclase3=0
nclase4=0

for (i in 1:n){
  if (dataset$vote_average[i]>0 & dataset$vote_average[i]<=36.4){
    dataset$clase_votos[i]<-"clase1"
    nclase1=nclase1+1
  }
  if (dataset$vote_average[i]>36.4 & dataset$vote_average[i]<=54.6){
    dataset$clase_votos[i]<-"clase2"
    nclase2=nclase2+1
  }
  if (dataset$vote_average[i]>54.6 & dataset$vote_average[i]<=72.8){
    dataset$clase_votos[i]<-"clase3"
    nclase3=nclase3+1
  }
  if (dataset$vote_average[i]>72.8 & dataset$vote_average[i]<=100){
    dataset$clase_votos[i]<-"clase4"
    nclase4=nclase4+1
  }
}
dataset$clase_votos<-as.factor(dataset$clase_votos) #pasar clase_votos a factor
dataset<-dataset[ ,-4] #sacar vote_average

Si se clasificara todo como la clase mayoritaria, se obtendrían métricas de acurrary de 0,7.

#clase 3 es la mayoritaria
(accu_clase_mayoritaria= nclase3/(nclase1+nclase2+nclase3+nclase4))
## [1] 0.7035611

Luego, se procedió a separar las bases en entrenamiento y testeo, 75% y 25% respectivamente.

data<-dataset
set.seed(27795)


#separar train and test
smp_size <- floor(0.75 * nrow(data))
train_ind <- sample(seq_len(nrow(data)), size = smp_size)
dataset_train <- data[train_ind, ]
dataset_test <- data[-train_ind, ]

Finalmente, se puede realizar el árbol de clasificación, no sin antes sacar variables como las ganancias(revenues) o cantidad de gente que votó(vote_count) que son datos que se tienen a posteriori, cuando lo que se gustaría predecir es el exito de la pelicula antes de que se tengan esos datos.

#train1  
train1<-dataset_train[ ,-2] #quitar revenues
train1<-train1[ ,-3] #quitar vote_count


#modelo con presupuesto
#fit1 = rpart(clase_votos ~ ., data=train1, method="class", cp= 0.001) 
fit1 = rpart(clase_votos ~ ., data=train1, method="class", cp= 0.0023) 
fancyRpartPlot(fit1)

fit1$variable.importance
##         runtime          genero ano_lanzamiento          budget 
##       54.812301       45.118965       36.377468       16.198139 
## mes_lanzamiento          ingles 
##        2.491000        1.089214
#predecir en base train 
train1$pred <- predict(fit1,data=train1)
train1$pred <- apply(train1$pred,1,which.max)

Luego pasamos a predecir con la base de testeo, verificar que estén las mismas variables.

#test1  
test1<-dataset_test[ ,-2] #quitar revenues
test1<-test1[ ,-3] #quitar vote_count
#predecir con la base de testeo
test1$pred <- predict(fit1,newdata=test1)
test1$pred <- apply(test1$pred,1,which.max)

Se crean las metricas respectivas.

confusion2=table(test1$clase_votos,test1$pred)
confusion2
##         
##            2   3   4
##   clase1   0   7   0
##   clase2   6 173   2
##   clase3   2 858  21
##   clase4   0 205  25
acurracy_testeo=((0+confusion2[2,1]+confusion2[3,2]+confusion2[4,3])/(0+confusion2[2,1]+confusion2[3,2]+confusion2[4,3]+confusion2[1,1]+confusion2[1,2]+confusion2[1,3]+confusion2[2,2]+confusion2[2,3]+confusion2[3,1]+confusion2[3,3]+confusion2[4,1]+confusion2[4,2]))
acurracy_testeo
## [1] 0.6843726

Como se puede ver en la tabla de confusión, no se clasificó ninguna película en la clase 1 por lo que se calcularon precision, recall y F1-score para las otras tres clases y el total general. El acurracy obtenido es de 0.684.

#clase 2
(precision2=(confusion2[2,1])/(confusion2[1,1]+confusion2[2,1]+confusion2[3,1]+confusion2[4,1]))  
## [1] 0.75
(recall2=(confusion2[2,1])/(confusion2[2,1]+confusion2[2,2]+confusion2[2,3]+0))
## [1] 0.03314917
(f1_2=(2*precision2*recall2)/(precision2+recall2))
## [1] 0.06349206
#clase 3
(precision3=(confusion2[3,2])/(confusion2[1,2]+confusion2[2,2]+confusion2[3,2]+confusion2[4,2]))  
## [1] 0.6902655
(recall3=(confusion2[3,2])/(confusion2[3,1]+confusion2[3,2]+confusion2[3,3]+0))
## [1] 0.9738933
(f1_3=(2*precision3*recall3)/(precision3+recall3))
## [1] 0.8079096
#clase 4
(precision4=(confusion2[4,3])/(confusion2[1,3]+confusion2[2,3]+confusion2[3,3]+confusion2[4,3]))  
## [1] 0.5208333
(recall4=(confusion2[4,3])/(confusion2[4,1]+confusion2[4,2]+confusion2[4,3]+0))
## [1] 0.1086957
(f1_4=(2*precision4*recall4)/(precision4+recall4))
## [1] 0.1798561
#general
(precision=(precision2+precision3+precision4+0)/4)
## [1] 0.4902747
(recall=(recall2+recall3+recall4)/4)
## [1] 0.2789345
(f1=(f1_2+f1_3+f1_4)/4)
## [1] 0.2628144
(f1_=(2*precision*recall)/(precision+recall))
## [1] 0.3555718

Posteriormente, se clasificó con otro modelo parecido al anterior pero quitando el presupuesto, la idea es comparar que tan importante es presupuesto en la clasificación.

#modelo sin presupuesto
train2<-dataset_train[ ,-2] #quitar revenues
train2<-train2[ ,-3] #quitar vote_count
train2<-train2[ ,-1] #quitar presupuesto

Primero se probó para un parámetro de complejidad cualquiera (cp=0.001) y luego se usó aquel que minimizaba los errores.

fit_prueba2 = rpart(clase_votos ~ ., data=train2, method="class", cp= 0.001) 
printcp(fit_prueba2)
## 
## Classification tree:
## rpart(formula = clase_votos ~ ., data = train2, method = "class", 
##     cp = 0.001)
## 
## Variables actually used in tree construction:
## [1] ano_lanzamiento genero          mes_lanzamiento runtime        
## 
## Root node error: 1123/3897 = 0.28817
## 
## n= 3897 
## 
##          CP nsplit rel error xerror     xstd
## 1 0.0068270      0   1.00000 1.0000 0.025177
## 2 0.0022262      3   0.97952 1.0036 0.025203
## 3 0.0020778      7   0.96883 1.0205 0.025328
## 4 0.0017809     20   0.94034 1.0365 0.025442
## 5 0.0014841     25   0.93143 1.0445 0.025498
## 6 0.0013357     36   0.91095 1.0534 0.025559
## 7 0.0010000     42   0.90294 1.0659 0.025644
fit2 = rpart(clase_votos ~ ., data=train2, method="class", cp= 0.0021) 
fancyRpartPlot(fit2)

fit2$variable.importance
##         runtime          genero ano_lanzamiento mes_lanzamiento 
##       41.906906       26.305109       25.554548        6.065308 
##          ingles 
##        1.334519

Prediciendo con la base de testeo.

test2<-dataset_test[ ,-2] #quitar revenues
test2<-test2[ ,-3] #quitar vote_count
test2<-test2[ ,-1]# quitar presupuesto

test2$pred <- predict(fit2,newdata=test2)
test2$pred <- apply(test2$pred,1,which.max)

Se calcularon las métricas respectivas.

#calculo de métricas
confusion3=table(test2$clase_votos,test2$pred)
confusion3
##         
##            3   4
##   clase1   7   0
##   clase2 180   1
##   clase3 873   8
##   clase4 216  14
#confusion2
acurracy_test2=((0+0+confusion3[3,1]+confusion3[4,2])/(0+confusion3[2,1]+confusion3[3,2]+confusion3[1,1]+confusion3[1,2]+confusion3[2,2]+confusion3[3,1]+confusion3[4,1]+confusion3[4,2]))
acurracy_test2
## [1] 0.6828329

Como se puede ver en la tabla de confusión, no se clasificó ninguna película en la clase 1 ni 2 por lo que se calcularon precision, recall y F1-score para las otras dos clases y el total general. El acurracy obtenido es de 0.682 muy parecido al anterior.

#clase 3
(prec3=(confusion3[3,1])/(confusion3[1,1]+confusion3[2,1]+confusion3[3,1]+confusion3[4,1]))  
## [1] 0.6841693
(reca3=(confusion3[3,1])/(confusion3[3,1]+confusion3[3,2]+0+0))
## [1] 0.9909194
(f1s_3=(2*prec3*reca3)/(prec3+reca3))
## [1] 0.8094576
#clase 4
(prec4=(confusion3[4,2])/(confusion3[1,2]+confusion3[2,2]+confusion3[3,2]+confusion3[4,2]))  
## [1] 0.6086957
(reca4=(confusion3[4,2])/(confusion3[4,1]+confusion3[4,2]+0+0))
## [1] 0.06086957
(f1s_4=(2*prec4*reca4)/(prec4+reca4))
## [1] 0.1106719
#general
(pre_sin=(prec3+prec4+0)/2)
## [1] 0.6464325
(reca_sin=(reca3+reca4)/2)
## [1] 0.5258945
(f1_sin=(f1s_3+f1s_4)/2)
## [1] 0.4600648

En general, con los datos disponibles no es posible predecir la clasificación de película exitosa o no, dados su bajos valores en métricas como el F1-score. Los resultados de este experimento muestran que incluir el presupuesto de la película ayuda pero no en gran medida, la diferencia de sus métricas es poca. Por otra parte, ninguno de los modelos obtuvo un accuracy mayor al de clasificar todos como la clase mayoritaria.

Untitled

Conclusión

A partir de los resultados de los experimentos hechos, se puede concluir que las películas de romance están muy mal clasificadas, mientras que las películas de misterio, acción y drama tienden a tener variadas clasificaciones. Al hacer un clasificador con los datos numéricos, los resultados de accuracy y f1-score son muy bajos, por lo que no es posible poder predecir con certeza si una película es buena o mala. Incluso al agregar datos categóricos no se llega a valores de f1-score suficientemente buenos como para poder decir que el clasificador predice de manera correcta la calificación. Es importante destacar que, si bien la regla de asociación fue la que obtuvo mejores resultados, se requiere de mayor información para poder decir que tan importante es un atributo de por sí con respecto a los otros atributos. Para un experimento futuro sería bueno intentar hacer la regla de asociación con otros atributos, e intentar rehacer el clasificador incluyendo aun más variables categóricas que pudieran servir.